Passed
Push — master ( b7cf3e...4763f6 )
by Rafael S.
03:31
created

wav-buffer-writer.js ➔ getLtxtChunkBytes_   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 12
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 13
rs 9.8
1
/*
2
 * Copyright (c) 2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview Make buffers of structured wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import {pack, packTo, packStringTo, packString} from '../vendor/byte-data.js';
31
import BufferIO from './bufferio.js';
32
33
let io = new BufferIO();
34
35
/**
36
 * Return a .wav file byte buffer with the data from the WaveFile object.
37
 * The return value of this method can be written straight to disk.
38
 * @return {!Uint8Array} The wav file bytes.
39
 * @private
40
 */
41
export default function writeWavBuffer(wav) {
42
  let uInt32_ = {bits: 32, be: false};
43
  let uInt16_ = {bits: 16, be: false};
44
  uInt16_.be = wav.container === 'RIFX';
45
  uInt32_.be = uInt16_.be;
46
  /** @type {!Array<!Array<number>>} */
47
  let fileBody = [
48
    getJunkBytes_(wav, uInt32_),
49
    getDs64Bytes_(wav, uInt32_),
50
    getBextBytes_(wav, uInt32_, uInt16_),
51
    getFmtBytes_(wav, uInt32_, uInt16_),
52
    getFactBytes_(wav, uInt32_),
53
    packString(wav.data.chunkId),
54
    pack(wav.data.samples.length, uInt32_),
55
    wav.data.samples,
56
    getCueBytes_(wav, uInt32_),
57
    getSmplBytes_(wav, uInt32_),
58
    getLISTBytes_(wav, uInt32_, uInt16_)
59
  ];
60
  /** @type {number} */
61
  let fileBodyLength = 0;
62
  for (let i=0; i<fileBody.length; i++) {
63
    fileBodyLength += fileBody[i].length;
64
  }
65
  /** @type {!Uint8Array} */
66
  let file = new Uint8Array(fileBodyLength + 12);
67
  /** @type {number} */
68
  let index = 0;
69
  index = packStringTo(wav.container, file, index);
70
  index = packTo(fileBodyLength + 4, uInt32_, file, index);
71
  index = packStringTo(wav.format, file, index);
72
  for (let i=0; i<fileBody.length; i++) {
73
    file.set(fileBody[i], index);
74
    index += fileBody[i].length;
75
  }
76
  return file;
77
}
78
79
/**
80
 * Return the bytes of the 'bext' chunk.
81
 * @return {!Array<number>} The 'bext' chunk bytes.
82
 * @private
83
 */
84
function getBextBytes_(wav, uInt32_, uInt16_) {
85
  /** @type {!Array<number>} */
86
  let bytes = [];
87
  enforceBext_(wav);
88
  if (wav.bext.chunkId) {
89
    wav.bext.chunkSize = 602 + wav.bext.codingHistory.length;
90
    bytes = bytes.concat(
91
      packString(wav.bext.chunkId),
92
      pack(602 + wav.bext.codingHistory.length, uInt32_),
93
      io.writeString_(wav.bext.description, 256),
94
      io.writeString_(wav.bext.originator, 32),
95
      io.writeString_(wav.bext.originatorReference, 32),
96
      io.writeString_(wav.bext.originationDate, 10),
97
      io.writeString_(wav.bext.originationTime, 8),
98
      pack(wav.bext.timeReference[0], uInt32_),
99
      pack(wav.bext.timeReference[1], uInt32_),
100
      pack(wav.bext.version, uInt16_),
101
      io.writeString_(wav.bext.UMID, 64),
102
      pack(wav.bext.loudnessValue, uInt16_),
103
      pack(wav.bext.loudnessRange, uInt16_),
104
      pack(wav.bext.maxTruePeakLevel, uInt16_),
105
      pack(wav.bext.maxMomentaryLoudness, uInt16_),
106
      pack(wav.bext.maxShortTermLoudness, uInt16_),
107
      io.writeString_(wav.bext.reserved, 180),
108
      io.writeString_(
109
        wav.bext.codingHistory, wav.bext.codingHistory.length));
110
  }
111
  return bytes;
112
}
113
114
/**
115
 * Make sure a 'bext' chunk is created if BWF data was created in a file.
116
 * @private
117
 */
118
function enforceBext_(wav) {
119
  for (var prop in wav.bext) {
120
    if (wav.bext.hasOwnProperty(prop)) {
121
      if (wav.bext[prop] && prop != 'timeReference') {
122
        wav.bext.chunkId = 'bext';
123
        break;
124
      }
125
    }
126
  }
127
  if (wav.bext.timeReference[0] || wav.bext.timeReference[1]) {
128
    wav.bext.chunkId = 'bext';
129
  }
130
}
131
132
/**
133
 * Return the bytes of the 'ds64' chunk.
134
 * @return {!Array<number>} The 'ds64' chunk bytes.
135
 * @private
136
 */
137
function getDs64Bytes_(wav, uInt32_) {
138
  /** @type {!Array<number>} */
139
  let bytes = [];
140
  if (wav.ds64.chunkId) {
141
    bytes = bytes.concat(
142
      packString(wav.ds64.chunkId),
143
      pack(wav.ds64.chunkSize, uInt32_),
144
      pack(wav.ds64.riffSizeHigh, uInt32_),
145
      pack(wav.ds64.riffSizeLow, uInt32_),
146
      pack(wav.ds64.dataSizeHigh, uInt32_),
147
      pack(wav.ds64.dataSizeLow, uInt32_),
148
      pack(wav.ds64.originationTime, uInt32_),
149
      pack(wav.ds64.sampleCountHigh, uInt32_),
150
      pack(wav.ds64.sampleCountLow, uInt32_));
151
  }
152
  //if (this.ds64.tableLength) {
153
  //  ds64Bytes = ds64Bytes.concat(
154
  //    pack(this.ds64.tableLength, this.uInt32_),
155
  //    this.ds64.table);
156
  //}
157
  return bytes;
158
}
159
160
/**
161
 * Return the bytes of the 'cue ' chunk.
162
 * @return {!Array<number>} The 'cue ' chunk bytes.
163
 * @private
164
 */
165
function getCueBytes_(wav, uInt32_) {
166
  /** @type {!Array<number>} */
167
  let bytes = [];
168
  if (wav.cue.chunkId) {
169
    /** @type {!Array<number>} */
170
    let cuePointsBytes = getCuePointsBytes_(wav, uInt32_);
171
    bytes = bytes.concat(
172
      packString(wav.cue.chunkId),
173
      pack(cuePointsBytes.length + 4, uInt32_),
174
      pack(wav.cue.dwCuePoints, uInt32_),
175
      cuePointsBytes);
176
  }
177
  return bytes;
178
}
179
180
/**
181
 * Return the bytes of the 'cue ' points.
182
 * @return {!Array<number>} The 'cue ' points as an array of bytes.
183
 * @private
184
 */
185
function getCuePointsBytes_(wav, uInt32_) {
186
  /** @type {!Array<number>} */
187
  let points = [];
188
  for (let i=0; i<wav.cue.dwCuePoints; i++) {
189
    points = points.concat(
190
      pack(wav.cue.points[i].dwName, uInt32_),
191
      pack(wav.cue.points[i].dwPosition, uInt32_),
192
      packString(wav.cue.points[i].fccChunk),
193
      pack(wav.cue.points[i].dwChunkStart, uInt32_),
194
      pack(wav.cue.points[i].dwBlockStart, uInt32_),
195
      pack(wav.cue.points[i].dwSampleOffset, uInt32_));
196
  }
197
  return points;
198
}
199
200
/**
201
 * Return the bytes of the 'smpl' chunk.
202
 * @return {!Array<number>} The 'smpl' chunk bytes.
203
 * @private
204
 */
205
function getSmplBytes_(wav, uInt32_) {
206
  /** @type {!Array<number>} */
207
  let bytes = [];
208
  if (wav.smpl.chunkId) {
209
    /** @type {!Array<number>} */
210
    let smplLoopsBytes = getSmplLoopsBytes_(wav, uInt32_);
211
    bytes = bytes.concat(
212
      packString(wav.smpl.chunkId),
213
      pack(smplLoopsBytes.length + 36, uInt32_),
214
      pack(wav.smpl.dwManufacturer, uInt32_),
215
      pack(wav.smpl.dwProduct, uInt32_),
216
      pack(wav.smpl.dwSamplePeriod, uInt32_),
217
      pack(wav.smpl.dwMIDIUnityNote, uInt32_),
218
      pack(wav.smpl.dwMIDIPitchFraction, uInt32_),
219
      pack(wav.smpl.dwSMPTEFormat, uInt32_),
220
      pack(wav.smpl.dwSMPTEOffset, uInt32_),
221
      pack(wav.smpl.dwNumSampleLoops, uInt32_),
222
      pack(wav.smpl.dwSamplerData, uInt32_),
223
      smplLoopsBytes);
224
  }
225
  return bytes;
226
}
227
228
/**
229
 * Return the bytes of the 'smpl' loops.
230
 * @return {!Array<number>} The 'smpl' loops as an array of bytes.
231
 * @private
232
 */
233
function getSmplLoopsBytes_(wav, uInt32_) {
234
  /** @type {!Array<number>} */
235
  let loops = [];
236
  for (let i=0; i<wav.smpl.dwNumSampleLoops; i++) {
237
    loops = loops.concat(
238
      pack(wav.smpl.loops[i].dwName, uInt32_),
239
      pack(wav.smpl.loops[i].dwType, uInt32_),
240
      pack(wav.smpl.loops[i].dwStart, uInt32_),
241
      pack(wav.smpl.loops[i].dwEnd, uInt32_),
242
      pack(wav.smpl.loops[i].dwFraction, uInt32_),
243
      pack(wav.smpl.loops[i].dwPlayCount, uInt32_));
244
  }
245
  return loops;
246
}
247
248
/**
249
 * Return the bytes of the 'fact' chunk.
250
 * @return {!Array<number>} The 'fact' chunk bytes.
251
 * @private
252
 */
253
function getFactBytes_(wav, uInt32_) {
254
  /** @type {!Array<number>} */
255
  let bytes = [];
256
  if (wav.fact.chunkId) {
257
    bytes = bytes.concat(
258
      packString(wav.fact.chunkId),
259
      pack(wav.fact.chunkSize, uInt32_),
260
      pack(wav.fact.dwSampleLength, uInt32_));
261
  }
262
  return bytes;
263
}
264
265
/**
266
 * Return the bytes of the 'fmt ' chunk.
267
 * @return {!Array<number>} The 'fmt' chunk bytes.
268
 * @throws {Error} if no 'fmt ' chunk is present.
269
 * @private
270
 */
271
function getFmtBytes_(wav, uInt32_, uInt16_) {
272
  /** @type {!Array<number>} */
273
  let fmtBytes = [];
274
  if (wav.fmt.chunkId) {
275
    return fmtBytes.concat(
276
      packString(wav.fmt.chunkId),
277
      pack(wav.fmt.chunkSize, uInt32_),
278
      pack(wav.fmt.audioFormat, uInt16_),
279
      pack(wav.fmt.numChannels, uInt16_),
280
      pack(wav.fmt.sampleRate, uInt32_),
281
      pack(wav.fmt.byteRate, uInt32_),
282
      pack(wav.fmt.blockAlign, uInt16_),
283
      pack(wav.fmt.bitsPerSample, uInt16_),
284
      getFmtExtensionBytes_(wav, uInt32_, uInt16_));
285
  }
286
  throw Error('Could not find the "fmt " chunk');
287
}
288
289
/**
290
 * Return the bytes of the fmt extension fields.
291
 * @return {!Array<number>} The fmt extension bytes.
292
 * @private
293
 */
294
function getFmtExtensionBytes_(wav, uInt32_, uInt16_) {
295
  /** @type {!Array<number>} */
296
  let extension = [];
297
  if (wav.fmt.chunkSize > 16) {
298
    extension = extension.concat(
299
      pack(wav.fmt.cbSize, uInt16_));
300
  }
301
  if (wav.fmt.chunkSize > 18) {
302
    extension = extension.concat(
303
      pack(wav.fmt.validBitsPerSample, uInt16_));
304
  }
305
  if (wav.fmt.chunkSize > 20) {
306
    extension = extension.concat(
307
      pack(wav.fmt.dwChannelMask, uInt32_));
308
  }
309
  if (wav.fmt.chunkSize > 24) {
310
    extension = extension.concat(
311
      pack(wav.fmt.subformat[0], uInt32_),
312
      pack(wav.fmt.subformat[1], uInt32_),
313
      pack(wav.fmt.subformat[2], uInt32_),
314
      pack(wav.fmt.subformat[3], uInt32_));
315
  }
316
  return extension;
317
}
318
319
/**
320
 * Return the bytes of the 'LIST' chunk.
321
 * @return {!Array<number>} The 'LIST' chunk bytes.
322
 */
323
function getLISTBytes_(wav, uInt32_, uInt16_) {
324
  /** @type {!Array<number>} */
325
  let bytes = [];
326
  for (let i=0; i<wav.LIST.length; i++) {
327
    /** @type {!Array<number>} */
328
    let subChunksBytes = getLISTSubChunksBytes_(
329
        wav.LIST[i].subChunks, wav.LIST[i].format, wav, uInt32_, uInt16_);
330
    bytes = bytes.concat(
331
      packString(wav.LIST[i].chunkId),
332
      pack(subChunksBytes.length + 4, uInt32_),
333
      packString(wav.LIST[i].format),
334
      subChunksBytes);
335
  }
336
  return bytes;
337
}
338
339
/**
340
 * Return the bytes of the sub chunks of a 'LIST' chunk.
341
 * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
342
 * @param {string} format The format of the 'LIST' chunk.
343
 *    Currently supported values are 'adtl' or 'INFO'.
344
 * @return {!Array<number>} The sub chunk bytes.
345
 * @private
346
 */
347
function getLISTSubChunksBytes_(subChunks, format, wav, uInt32_, uInt16_) {
348
  /** @type {!Array<number>} */
349
  let bytes = [];
350
  for (let i=0; i<subChunks.length; i++) {
351
    if (format == 'INFO') {
352
      bytes = bytes.concat(
353
        packString(subChunks[i].chunkId),
354
        pack(subChunks[i].value.length + 1, uInt32_),
355
        io.writeString_(
356
          subChunks[i].value, subChunks[i].value.length));
357
      bytes.push(0);
358
    } else if (format == 'adtl') {
359
      if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
360
        bytes = bytes.concat(
361
          packString(subChunks[i].chunkId),
362
          pack(
363
            subChunks[i].value.length + 4 + 1, uInt32_),
364
          pack(subChunks[i].dwName, uInt32_),
365
          io.writeString_(
366
            subChunks[i].value,
367
            subChunks[i].value.length));
368
        bytes.push(0);
369
      } else if (subChunks[i].chunkId == 'ltxt') {
370
        bytes = bytes.concat(
371
          getLtxtChunkBytes_(subChunks[i], wav, uInt32_, uInt16_));
372
      }
373
    }
374
    if (bytes.length % 2) {
375
      bytes.push(0);
376
    }
377
  }
378
  return bytes;
379
}
380
381
/**
382
 * Return the bytes of a 'ltxt' chunk.
383
 * @param {!Object} ltxt the 'ltxt' chunk.
384
 * @return {!Array<number>} The 'ltxt' chunk bytes.
385
 * @private
386
 */
387
function getLtxtChunkBytes_(ltxt, wav, uInt32_, uInt16_) {
388
  return [].concat(
389
    packString(ltxt.chunkId),
390
    pack(ltxt.value.length + 20, uInt32_),
391
    pack(ltxt.dwName, uInt32_),
392
    pack(ltxt.dwSampleLength, uInt32_),
393
    pack(ltxt.dwPurposeID, uInt32_),
394
    pack(ltxt.dwCountry, uInt16_),
395
    pack(ltxt.dwLanguage, uInt16_),
396
    pack(ltxt.dwDialect, uInt16_),
397
    pack(ltxt.dwCodePage, uInt16_),
398
    io.writeString_(ltxt.value, ltxt.value.length));
399
}
400
401
/**
402
 * Return the bytes of the 'junk' chunk.
403
 * @return {!Array<number>} The 'junk' chunk bytes.
404
 * @private
405
 */
406
function getJunkBytes_(wav, uInt32_) {
407
  /** @type {!Array<number>} */
408
  let bytes = [];
409
  if (wav.junk.chunkId) {
410
    return bytes.concat(
411
      packString(wav.junk.chunkId),
412
      pack(wav.junk.chunkData.length, uInt32_),
413
      wav.junk.chunkData);
414
  }
415
  return bytes;
416
}
417